#include "vtkFunctions_export.hpp"
#include "vtkConstants.hpp"
#include "vtkFunctions.hpp"
#include "fieldFunctions.hpp"
#include "boundaryPatch.hpp"

namespace hms::vtk {


std::stringstream getConverter(){
	std::stringstream converter;
	converter << std::setprecision( hms::io::writePrecision );
	return converter;
}


xml::XMLElement* createDataArray(
	xml::XMLElement* parent, std::string&& type
){
	xml::XMLElement* dataArray = parent->InsertNewChildElement("DataArray");
	dataArray->SetAttribute( "type", type.c_str() );
	dataArray->SetAttribute( "format", "ascii" );
	return dataArray;	
}


void toStream( const xml::XMLDocument* doc, std::ostream& os ){
	xml::XMLPrinter streamer;
	doc->Print( &streamer );
	os << streamer.CStr();
}

void toFile( const xml::XMLDocument* doc, const fs::path& filePath ){
	std::ofstream file { getWriteFile( filePath, false ) };
	toStream(doc, file);
}

void toFile(
	const xml::XMLDocument* doc,
	const fs::path& path,
	const MeshVariant& meshVar
){
	fs::path copy {path};
	toFile( doc, replaceExtension(copy, meshVar) );
}

template<typename MeshType>
fs::path& replaceExtension( fs::path& path ){
	return path.replace_extension( getVtkExtension<MeshType>() );
}

template<typename MeshType>
fs::path& replaceExtension( fs::path& path, const MeshBase<MeshType>& ){
	return replaceExtension<MeshType>(path);
}


fs::path& replaceExtension( fs::path& path, const hms::MeshVariant& meshVar ){
	std::visit( [&](const auto& mesh){
		replaceExtension(path, mesh);
	}, meshVar);
	return path;
}


xml::XMLElement* createRootNode( xml::XMLDocument* doc, const char* name ){
	xml::XMLDeclaration* decl { doc->NewDeclaration(
		"xml version=\"1.0\" standalone=\"yes\""
	) };
	xml::XMLElement* root { doc->NewElement(name) };
	doc->InsertFirstChild(root);
	doc->InsertFirstChild(decl);
	return root;
}

xml::XMLElement* createVtkNode( xml::XMLDocument* doc ){
	xml::XMLElement* vtk { createRootOrInsert(doc, rootName) };
	vtk->SetAttribute( "version", "0.1" );
	vtk->SetAttribute( "byte_order", "LittleEndian" );
	return vtk;
}

xml::XMLElement* createRootOrInsert( xml::XMLNode* node, const char* name ){
	if ( xml::XMLDocument* doc = node->ToDocument() )
		return createRootNode( doc, name );
	else if ( xml::XMLElement* elem = node->ToElement() )
		return elem->InsertNewChildElement(name);
	else
		throw std::logic_error( "Could not cast node to document or element!" );
}

xml::XMLElement* getRootOrInsert( xml::XMLNode* node, const char* name ){
	if ( xml::XMLDocument* doc = node->ToDocument() ){
		if ( xml::XMLElement* elem = node->FirstChildElement(name) )
			return elem;
		else
			return createRootNode( doc, name );
	}
	else if ( xml::XMLElement* elem = node->ToElement() )
		return findOrInsert(elem, name);
	else
		throw std::logic_error( "Could not cast node to document or element!" );
}

xml::XMLElement* findOrInsert( xml::XMLElement* elem, const char* name ){
	if ( xml::XMLElement* existing = elem->FirstChildElement(name) )
		return existing;
	else
		return elem->InsertNewChildElement(name);
}


void writeFunction(
	xml::XMLElement* parent,
	const std::string& functionString,
	Index component
){
	xml::XMLElement* fElem {
		parent->InsertNewChildElement("function")
	};
	fElem->SetAttribute("component", component);
	fElem->SetText( functionString.c_str() );
}


void writeFunction(
	xml::XMLElement* parent,
	const FunctionParserWrapper& fp,
	Index component
){
	writeFunction(parent, fp.functionString(), component );
}

void writeFunctions(
	xml::XMLElement* parent,
	const std::vector<FunctionParserWrapper>& fparsers
){
	for ( size_t i{0}; i<fparsers.size(); ++i ){
		writeFunction( parent, fparsers[i], i );
	}
}



namespace internal {

template<bool domainOnly = false, typename MeshType = UniMesh>
void writeTimeSeries(
	xml::XMLElement* parent,
	const TimeSeries& timeSeries,
	[[maybe_unused]]
	std::experimental::observer_ptr<const MeshBase<MeshType>> mesh = nullptr
){
	parent->SetAttribute( "NumberOfTimeValues", timeSeries.size() );
	for ( const std::pair<scalar, ArrayXXs>& valuePair : timeSeries ){
		xml::XMLElement* valElem {
			parent->InsertNewChildElement("Values")
		};
		valElem->SetAttribute( "Time", valuePair.first );
		if constexpr (domainOnly){
			// static_assert( std::is_base_of_v<MeshBase<MeshType>, MeshType> );
			assert( mesh );
			matrixToText(
				valElem,
				domainValues( valuePair.second, *mesh )
			);
		} else {
			matrixToText( valElem , valuePair.second);
		}
	}
}

}


template<Uniformity isUniform, typename MeshType>
void writeTimeSeries(
	xml::XMLElement* parent,
	const TimeSeries& timeSeries,
	[[maybe_unused]] const MeshBase<MeshType>& mesh
){
	internal::writeTimeSeries<!isUniform>(
		parent,
		timeSeries,
		std::experimental::make_observer( &mesh)
	);
}


void writeTimeSeries(
	xml::XMLElement* parent,
	const TimeSeries& timeSeries
){
	internal::writeTimeSeries<false>(parent, timeSeries);
}



void createBindingDoc( xml::XMLDocument* doc ){
	xml::XMLElement* vtk { createVtkNode(doc) };
	vtk->SetAttribute( "type", "Collection");
	vtk->InsertNewChildElement("Collection");
}

std::unique_ptr<xml::XMLDocument> createBindingDoc(){
	auto doc { std::make_unique<xml::XMLDocument>() };
	createBindingDoc( doc.get() );
	return doc;
}


void updateBindingFile(
	xml::XMLDocument* bindingFile,
	const fs::path& bindingFilePath,
	const Time& time,
	const fs::path& vtkFilePath
){
	xml::XMLElement* coll {
		getChildNodeByName( getVtkNode(bindingFile), "Collection" )
	};
	xml::XMLElement* dataset { coll->InsertNewChildElement("DataSet") };
	dataset->SetAttribute( "timestep", time.current() );
	dataset->SetAttribute( "file", vtkFilePath.filename().c_str() );

	toFile( bindingFile, bindingFilePath );
}


#define INSTANTIATE_FOR_ALL_MESHTYPES(MeshType) \
template void writeTimeSeries<Uniform>( xml::XMLElement*, \
	const TimeSeries&, const MeshBase<MeshType>& ); \
template void writeTimeSeries<NonUniform>( xml::XMLElement*, \
	const TimeSeries&, const MeshBase<MeshType>& ); \
template fs::path& replaceExtension( fs::path&, const MeshBase<MeshType>& ); \
template fs::path& replaceExtension<MeshType>( fs::path& );
#include "meshInstantiator.hpp"


} // end namespace hms::vtk